package cn.itcast.nems.journal.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.approval.bpm.eventstrategy.BpmEventStrategy;
import cn.itcast.nems.approval.dao.entity.ApprovalConfig;
import cn.itcast.nems.approval.dao.entity.ApprovalForm;
import cn.itcast.nems.approval.enumeration.ApprovalErrorBodyEnum;
import cn.itcast.nems.approval.enumeration.ApprovalFormStateEnum;
import cn.itcast.nems.approval.service.ApprovalConfigService;
import cn.itcast.nems.approval.service.ApprovalFormFlowService;
import cn.itcast.nems.approval.service.ApprovalFormService;
import cn.itcast.nems.approval.service.ApprovalFormService.ApproveResult;
import cn.itcast.nems.approval.service.ApprovalFormService.JournalType;
import cn.itcast.nems.base.api.ClazzApi;
import cn.itcast.nems.base.dao.entity.Itcastschool;
import cn.itcast.nems.base.dao.entity.Subject;
import cn.itcast.nems.base.dao.mapper.ItcastschoolMapper;
import cn.itcast.nems.base.dao.mapper.SubjectMapper;
import cn.itcast.nems.base.dto.ClazzDTO;
import cn.itcast.nems.common.PageQueryModel;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.common.constant.DiscountTypeEnum;
import cn.itcast.nems.common.constant.TeachingModelEnum;
import cn.itcast.nems.common.enumeration.BizNumberTypeEnum;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.service.AfterTransactionCommitExecutor;
import cn.itcast.nems.common.service.BizNumberService;
import cn.itcast.nems.common.utils.WrapperUtil;
import cn.itcast.nems.discounts.dao.entiry.EmsDiscounts;
import cn.itcast.nems.discounts.dao.mapper.EmsDiscountsMapper;
import cn.itcast.nems.employee.dao.entity.Employee;
import cn.itcast.nems.employee.dto.EmployeeDTO;
import cn.itcast.nems.employee.service.EmployeeService;
import cn.itcast.nems.journal.api.JournalRenewalApi;
import cn.itcast.nems.journal.dto.*;
import cn.itcast.nems.journal.enumeration.JournalStateEnum;
import cn.itcast.nems.journal.service.JournalRenewalDiscountService;
import cn.itcast.nems.journal.service.JournalRenewalLineService;
import cn.itcast.nems.journal.service.JournalRenewalService;
import cn.itcast.nems.journal.util.JournalRenewalUtil;
import cn.itcast.nems.journal.util.JournalUtil;
import cn.itcast.nems.manager.activity.BpmWorkflowManager;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.oldems.dao.entiry.Student;
import cn.itcast.nems.oldems.dao.mapper.StudentMapper;
import cn.itcast.nems.oldems.dto.StudentRecordCirculation;
import cn.itcast.nems.oldems.dto.oldapi.EmsApiResult;
import cn.itcast.nems.oldems.service.sync.OldEmsSyncService;
import cn.itcast.nems.order.api.JournalDiscountApi;
import cn.itcast.nems.order.dto.JournalDiscountApiReqDTO;
import cn.itcast.nems.order.dto.JournalDiscountApiResDTO;
import cn.itcast.nems.product.scheduler.TuitionFeeService;
import cn.itcast.nems.studentrecord.bo.JournalClazzModel;
import cn.itcast.nems.studentrecord.constant.DiscountScopeTypeEnum;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import cn.itcast.nems.studentrecord.converter.StudentRecordConverter;
import cn.itcast.nems.studentrecord.dao.condition.ApprovalType;
import cn.itcast.nems.studentrecord.dao.dataobject.JournalRenewalDO;
import cn.itcast.nems.studentrecord.dao.dataobject.JournalRenewalLineDiscountDO;
import cn.itcast.nems.studentrecord.dao.entity.*;
import cn.itcast.nems.studentrecord.dao.mapper.*;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeResultDTO;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordStateEnum;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordRenewalService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.validation.constraints.NotNull;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 升级凭单业务接口实现类
 * </p>
 *
 * @author zhouwei
 * @since 2023/12/8 2:28 PM
 **/
@RequiredArgsConstructor
@Service
@Slf4j
public class JournalRenewalServiceImpl extends ServiceImpl<JournalRenewalMapper, JournalRenewal>
        implements JournalRenewalService, JournalRenewalApi, BpmEventStrategy {

    private final BizNumberService bizNumberService;
    private final TuitionFeeService tuitionFeeService;
    private final SubjectMapper subjectMapper;
    private final ItcastschoolMapper itcastschoolMapper;
    private final EmsDiscountsMapper emsDiscountsMapper;
    private final JournalRenewalDiscountService journalRenewalDiscountService;
    private final JournalRenewalLineDiscountMapper journalRenewalLineDiscountMapper;
    private final JournalRenewalLineMapper journalRenewalLineMapper;
    private final BpmWorkflowManager bpmWorkflowManager;
    private final StudentRecordMapper studentRecordMapper;
    private final StudentRecordService studentRecordService;
    private final StudentRecordRenewalService studentRecordRenewalService;
    private final JournalRenewalLineService journalRenewalLineService;
    private final JournalRenewalOrderRelationshipMapper journalRenewalOrderRelationshipMapper;
    private final ApprovalFormService approvalFormService;
    private final ApprovalFormFlowService approvalFormFlowService;
    private final ApprovalConfigService approvalConfigService;
    private final StudentMapper studentMapper;
    private final EmployeeService employeeService;
    private final JournalDiscountApi journalDiscountApi ;
    private final ClazzApi clazzApi ;
    private final OldEmsSyncService oldEmsSyncService ;
    private final AfterTransactionCommitExecutor executor;

    @Override
    public IPage<JournalRenewalDO> findPageList(PageQueryModel<JournalRenewalModel> queryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalRenewalDO> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        JournalRenewalModel renewalModel = Objects.nonNull(queryModel.getCondition()) ? queryModel.getCondition() : new JournalRenewalModel();
        JournalRenewalDO renewalDO = CustomBeanUtil.copyProperties(renewalModel, new JournalRenewalDO());
        ApprovalType approvalType = renewalModel.getApprovalType();
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), renewalDO, JournalType.RENEWAL.name(), approvalType);
        if (approvalType == ApprovalType.ALL) {
            if (!AccountHolder.hasEmployeeGroups()) {
                return page;
            }
            renewalDO.setEmployeeGroups(AccountHolder.getEmployeeGroups());
        }
        return getBaseMapper().findPageList(page, renewalDO);
    }

    private List<String> getJournalIdFormIds() {
        ApprovalConfig config = approvalConfigService.getByJournalCode(JournalType.RENEWAL.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, ApprovalFormService.JournalType.RENEWAL.getCnName()));
        return bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccount());
    }

    @Override
    public cn.itcast.ic.common.bean.Page<JournalClazzDTO> findClazzList4PageSearch(PageQueryModel<JournalClazzModel> queryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalRenewalDO> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        JournalClazzModel clazzModel = Objects.nonNull(queryModel.getCondition()) ? queryModel.getCondition() : new JournalClazzModel();
        JournalRenewalDO renewalDO = new JournalRenewalDO();
        renewalDO.setTargetClazzName(clazzModel.getClazzName());
        ApprovalType approvalType = clazzModel.getApprovalType();
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), renewalDO, JournalType.RENEWAL.name(), approvalType);
        if (approvalType == ApprovalType.ALL) {
            if (!AccountHolder.hasEmployeeGroups()) {
                log.warn("【升级凭单】班级查询条件：没有用户组权限！");
                return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), 0, null);
            }
            renewalDO.setEmployeeGroups(AccountHolder.getEmployeeGroups());
        }
        IPage<JournalRenewalDO> pageData = getBaseMapper().findClazzPageList(page, renewalDO);
        List<JournalClazzDTO> list = CollectionUtil.isNotEmpty(pageData.getRecords()) ?
                pageData.getRecords().stream().map(item -> new JournalClazzDTO(item.getTargetClazzId(), item.getTargetClazzName())).toList() :
                null;
        return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), Long.valueOf(pageData.getTotal()).intValue(), list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JournalRenewalDTO create(JournalRenewalDTO renewalDTO) {
        this.validateCreateParam(renewalDTO);
        // 如果是修改，  处理历史数据
        if (Objects.nonNull(renewalDTO.getId())) {
            this.processBeforeUpdateHistoryData(renewalDTO.getId());
        }
        JournalRenewal journalRenewal = JournalRenewalUtil.wrapJournalRenewal(renewalDTO,
                Objects.requireNonNullElse(renewalDTO.getId(), bizNumberService.next(BizNumberTypeEnum.RENEWAL)));
        renewalDTO.setId(journalRenewal.getId());
        // 加载优惠项
        List<JournalRenewalDiscount> discountList = this.wrapJournalRenewalDiscount(renewalDTO, journalRenewal.getTargetClazzPrice());
        // 订单头优惠金额
        int discountMoney = CollectionUtil.isEmpty(discountList) ? 0 : discountList.stream().mapToInt(JournalRenewalDiscount::getDiscountAmount).sum();
        // 持久化订单行及行优惠项
        renewalDTO.setId(journalRenewal.getId());
        renewalDTO.setState(journalRenewal.getState());
        JournalRenewalProcessResultDTO resultDTO  = this.wrapJournalRenewalLine(renewalDTO, discountMoney, true, false);
        List<JournalRenewalLine> lines = Objects.requireNonNullElse(resultDTO.getLines(), new ArrayList<>());
        List<JournalRenewalLineDiscount> lineDiscounts = Objects.requireNonNullElse(resultDTO.getLineDiscounts(), new ArrayList<>());
        for (JournalRenewalLineDiscount lineDiscount : lineDiscounts) {
            journalRenewalLineDiscountMapper.insert(lineDiscount);
        }
        for (JournalRenewalLine line : lines) {
            journalRenewalLineMapper.insert(line);
        }
        journalRenewalDiscountService.saveBatch(discountList);
        this.saveOrUpdate(journalRenewal);
        if (CollectionUtils.isEmpty(renewalDTO.getLineList())) {
            renewalDTO.setLineList(Collections.EMPTY_LIST);
        }
        return renewalDTO;
    }

    /**
     * 保存或提交之前 ， 需要取消历史记录（头优惠记录、行记录和行优惠等）
     *
     * @param renewalId 升级凭单头ID
     **/
    private void processBeforeUpdateHistoryData(String renewalId) {
        List<JournalRenewalDiscount> discountList = journalRenewalDiscountService.list(
                                                    new LambdaQueryWrapper<JournalRenewalDiscount>()
                                                            .eq(JournalRenewalDiscount::getJournalRenewalId, renewalId)
                                                            .eq(JournalRenewalDiscount::getDeletedFlag, false)
        );
        if (CollectionUtils.isNotEmpty(discountList)) {
            log.info("【保存草稿】查询修改之前的升级凭单头优惠项：{}", JSON.toJSONString(discountList));
            Assert.isTrue(
                    journalRenewalDiscountService.update(
                                                    new LambdaUpdateWrapper<JournalRenewalDiscount>()
                                                            .in(JournalRenewalDiscount::getId, discountList.stream().map(JournalRenewalDiscount::getId).toList())
                                                            .set(JournalRenewalDiscount::getDeletedFlag, true)
                    ),
                    BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, "删除历史升级凭单头优惠项")
            );
        }
        List<JournalRenewalLine> lineList = journalRenewalLineService.list(
                                                    new LambdaQueryWrapper<JournalRenewalLine>()
                                                            .eq(JournalRenewalLine::getJournalRenewalId, renewalId)
                                                            .eq(JournalRenewalLine::getDeletedFlag, false)
        );
        if (CollectionUtils.isNotEmpty(lineList)) {
            log.info("【保存草稿】查询修改之前的升级凭单学员列表：{}", JSON.toJSONString(lineList));
            List<String> lineIds = lineList.stream().map(JournalRenewalLine::getId).toList();
            journalRenewalLineDiscountMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalRenewalLineDiscount>()
                                                            .eq(JournalRenewalLineDiscount::getJournalRenewalId, renewalId)
                                                            .in(JournalRenewalLineDiscount::getJournalRenewalLineId, lineIds)
                                                            .set(JournalRenewalLineDiscount::getDeletedFlag, true)
            );

            List<JournalRenewalOrderRelationship> orderRelationships = journalRenewalOrderRelationshipMapper.selectList(
                                                    new LambdaQueryWrapper<JournalRenewalOrderRelationship>()
                                                            .eq(JournalRenewalOrderRelationship::getJournalRenewalId, renewalId)
                                                            .in(JournalRenewalOrderRelationship::getJournalRenewalLineId, lineIds)
                                                            .eq(JournalRenewalOrderRelationship::getDeletedFlag, false)
            );
            if (CollectionUtils.isNotEmpty(orderRelationships)) {
                log.info("【保存草稿】查询修改之前的升级凭单学员订单关系数据：{}", JSON.toJSONString(orderRelationships));
                Assert.isTrue(
                        journalRenewalOrderRelationshipMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalRenewalOrderRelationship>()
                                                            .in(JournalRenewalOrderRelationship::getId, orderRelationships.stream().map(JournalRenewalOrderRelationship::getId).toList())
                                                            .set(JournalRenewalOrderRelationship::getDeletedFlag, true)
                        ) > 0,
                        BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, "删除升级凭单学员订单关系")
                );
            }
            Assert.isTrue(
                    journalRenewalLineMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalRenewalLine>()
                                                            .in(JournalRenewalLine::getId, lineIds).set(JournalRenewalLine::getDeletedFlag, true)
                    ) > 0,
                    BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, "删除历史升级凭单学员列表")
            );
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JournalRenewalDTO flow(JournalRenewalDTO renewalDTO) {
        String renewalId =  renewalDTO.getId() ;
        this.validateCreateParam(renewalDTO);
        JournalRenewal journalRenewal = JournalRenewalUtil.wrapJournalRenewal(renewalDTO,
                Objects.requireNonNullElse(renewalDTO.getId(), bizNumberService.next(BizNumberTypeEnum.RENEWAL)));
        renewalDTO.setId(journalRenewal.getId());
        // 加载优惠项
        List<JournalRenewalDiscount> discountList = this.wrapJournalRenewalDiscount(renewalDTO, journalRenewal.getTargetClazzPrice());
        // 订单头优惠金额
        int discountMoney = CollectionUtil.isEmpty(discountList) ? 0 : discountList.stream().mapToInt(JournalRenewalDiscount::getDiscountAmount).sum();
        // journalRenewalDiscountService.saveBatch(discountList);
        journalRenewal.setState(JournalStateEnum.PENDING.name());
        renewalDTO.setState(journalRenewal.getState());
        // 持久化订单行及行优惠项
        JournalRenewalProcessResultDTO resultDTO = this.wrapJournalRenewalLine(renewalDTO, discountMoney, true, true);
        List<JournalRenewalLine> lines = Objects.requireNonNullElse(resultDTO.getLines(), new ArrayList<>());
        List<JournalLineErrorDTO>  lineErrorList = this.lockOriginStudentRecord(lines, journalRenewal.getTargetClazzId());
        boolean insetFlag =  CollectionUtil.isEmpty(lineErrorList);
        if (insetFlag) {
            // 如果是修改，需要处理历史数据
            if (Objects.nonNull(renewalDTO.getId())) {
                this.processBeforeUpdateHistoryData(renewalDTO.getId());
            }
            journalRenewalDiscountService.saveBatch(discountList);
            List<JournalRenewalLineDiscount> lineDiscounts = Objects.requireNonNullElse(resultDTO.getLineDiscounts(), new ArrayList<>());
            for (JournalRenewalLineDiscount lineDiscount : lineDiscounts) {
                journalRenewalLineDiscountMapper.insert(lineDiscount);
            }
            for (JournalRenewalLine line : lines) {
                journalRenewalLineMapper.insert(line);
            }
            this.saveOrUpdate(journalRenewal);
            // this.lockOriginStudentRecord(lines, journalRenewal.getTargetClazzId());
            // this.saveOrUpdate(journalRenewal);
            ApprovalConfig config = approvalConfigService.getByJournalCode(JournalType.RENEWAL.name());
            Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, JournalType.RENEWAL.getCnName()));
            HashMap<String, Object> variables = JournalRenewalUtil.getVariables(
                    lines.stream().map(JournalRenewalLine::getOriginStudentRecordId).toList(),
                    journalRenewal.getTargetClazzId(),
                    journalRenewal.getSpecialFlag());
            approvalFormService.start(ApprovalFormService.JournalType.RENEWAL, journalRenewal.getId(), variables, config.getBpmProcessKey());
        }
        if(!insetFlag) {
            renewalDTO.setId(renewalId);
            renewalDTO.setErrorInfo(
                    new JournalErrorDTO(
                            StudentRecordErrorBodyEnum.JOURNAL_NOT_ALLOW_COMMIT.getMessage(),
                            lineErrorList
                    )
            );
        }
        // renewalDTO.setId(journalRenewal.getId());
        if (CollectionUtils.isEmpty(renewalDTO.getLineList())) {
            renewalDTO.setLineList(Collections.EMPTY_LIST);
        }
        return renewalDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JournalRenewalDTO flow(String id) {
        JournalRenewal journalRenewal = getBaseMapper().selectById(id);
        log.info("【提交】根据ID查询升级凭单头 ，ID：{}，查询结果：{}", id, JSON.toJSONString(journalRenewal));
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST,"升级凭单ID："+  id) ) ;
        JournalRenewalDTO renewalDTO = CustomBeanUtil.copyProperties(journalRenewal ,new JournalRenewalDTO()) ;
        Assert.isTrue(JournalStateEnum.DRAFT.name().equalsIgnoreCase(journalRenewal.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_JOURNAL_RENEWAL_STATE_ERROR, id, journalRenewal.getState()));
        List<JournalRenewalLine> lines = journalRenewalLineMapper.selectList(
                new LambdaQueryWrapper<JournalRenewalLine>().eq(JournalRenewalLine::getJournalRenewalId, id).orderByAsc(JournalRenewalLine::getCreatedDatetime));
        // 验证个人优惠单是否被使用
        List<JournalRenewalLineDiscountDO> discountList = journalRenewalLineDiscountMapper.findDiscountsByRenewalId(id, DiscountScopeTypeEnum.PERSONAL.name());
        List<JournalDiscountApiResDTO> apiResList = Objects.requireNonNullElse(discountList, new ArrayList<JournalRenewalLineDiscountDO>()).stream().map(
                item -> this.getPersonDiscount(journalRenewal.getTargetClazzId(), item.getOriginStudentRecordId(), item.getJournalDiscountLineId(), true)
        ).toList();
        log.info("【根据ID提交审批】个人优惠单列表：{}", JSON.toJSONString(apiResList));
        Assert.notEmpty(lines, BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "学生列表"));
        List<JournalLineErrorDTO> lineErrorList = this.lockOriginStudentRecord(lines,journalRenewal.getTargetClazzId());
        if(CollectionUtil.isNotEmpty(lineErrorList)){
            renewalDTO.setErrorInfo(new JournalErrorDTO(StudentRecordErrorBodyEnum.JOURNAL_NOT_ALLOW_COMMIT.getMessage() ,lineErrorList));
            return  renewalDTO ;
        }
        ApprovalConfig config = approvalConfigService.getByJournalCode(JournalType.RENEWAL.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, JournalType.RENEWAL.getCnName()));
        HashMap<String, Object> variables = JournalRenewalUtil.getVariables(
                lines.stream().map(JournalRenewalLine::getOriginStudentRecordId).toList(),
                journalRenewal.getTargetClazzId(),
                journalRenewal.getSpecialFlag()
        );
        String result = approvalFormService.start(ApprovalFormService.JournalType.RENEWAL, journalRenewal.getId(), variables, config.getBpmProcessKey());
        log.info("【提交】流程审批单详情：{}", result);
        int updateResult = getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRenewal>().eq(JournalRenewal::getId, id).set(JournalRenewal::getState, JournalStateEnum.PENDING.name()));
        Assert.isTrue(updateResult > 0, BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, "升级凭单头ID：" + id));
        renewalDTO.setState(JournalStateEnum.PENDING.name());
        return renewalDTO;
    }

    /**
     * 将升级凭单-行对应的学籍标识为 ”上锁“状态
     *
     * @param lines 升级凭单-行数据
     */
    private List<JournalLineErrorDTO> lockOriginStudentRecord(@NotNull List<JournalRenewalLine> lines , Integer targetClazzId ) {
        List<JournalLineErrorDTO> list =new ArrayList<>( );
        lines.forEach(item-> {
            JournalLineErrorDTO lineErrorDTO = this.validateStudentRecord(
                    new JournalRenewalLineDTO(), item.getOriginStudentRecordId() ,
                    item ,targetClazzId,true
            );
            if (Objects.nonNull(lineErrorDTO)) {
                list.add(lineErrorDTO);
            }
        });
        if(CollectionUtils.isEmpty(list)) {
            lines.forEach(item -> {
                studentRecordService.locks(Set.of(item.getOriginStudentRecordId()), ReferenceTypeEnum.RENEWAL, item.getJournalRenewalId());
            });
        }
        return list ;
    }

    @Override
    public JournalRenewalDTO findById(String id) {
        JournalRenewal journalRenewal = getBaseMapper().selectById(id);
        log.info("【查看升级凭单详情】 ID：{} , 查询结果：{}", id, JSON.toJSONString(journalRenewal));
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + id));
        JournalRenewalDTO journalRenewalDTO = CustomBeanUtil.copyProperties(journalRenewal, new JournalRenewalDTO());
        journalRenewalDTO.setTargetClazzPrice(Objects.nonNull(journalRenewal.getTargetClazzPrice()) ?
                BigDecimal.valueOf(journalRenewal.getTargetClazzPrice()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
                : null
        );
        // 查询 升级凭单头 折扣信息
        List<JournalRenewalDiscountDTO> discountList = journalRenewalDiscountService.findListByJournalRenewalId(id);
        BigDecimal targetClazzDiscountAmount = CollectionUtil.isNotEmpty(discountList) ?
                discountList.stream()
                        .map(JournalRenewalDiscountDTO::getDiscountAmount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add) : BigDecimal.valueOf(0);
        //  获取审批单 created_datetime
        ApprovalForm approvalForm = approvalFormService.getOne(
                new WrapperUtil<ApprovalForm>()
                        .limitLaseOne("created_datetime").eq(ApprovalForm::getJournalId, id)
                        .eq(ApprovalForm::getDeletedFlag, false)
                        .notIn(ApprovalForm::getState, ApprovalFormStateEnum.CANCELED.name()));
        journalRenewalDTO.setFormId(Objects.nonNull(approvalForm) ? approvalForm.getId() : null);
        journalRenewalDTO.setTargetClazzDiscountAmount(targetClazzDiscountAmount);
        journalRenewalDTO.setTargetClazzAmountDue(journalRenewalDTO.getTargetClazzPrice().subtract(journalRenewalDTO.getTargetClazzDiscountAmount()));
        journalRenewalDTO.setDiscountList(discountList);
        journalRenewalDTO.setLineList(Collections.EMPTY_LIST);
        Employee employee = employeeService.findOneByEmail(journalRenewal.getCreatedBy());
        if (Objects.nonNull(employee)) {
            EmployeeDTO employeeDTO = CustomBeanUtil.copyProperties(employee, new EmployeeDTO());
            journalRenewalDTO.setCreatedBy(employee.getName() + "（" + employee.getEmail() + "）");
            journalRenewalDTO.setEmployee(employeeDTO);
        }
        return journalRenewalDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(String id) {
        JournalRenewal journalRenewal = getBaseMapper().selectById(id);
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "ID：" + id));
        Assert.isTrue(AccountHolder.getRequiredAccount().equalsIgnoreCase(journalRenewal.getCreatedBy()), BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_DELETE_UNAUTHORIZED));
        Assert.isTrue(JournalStateEnum.DRAFT.name().equalsIgnoreCase(journalRenewal.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_DRAFT_DELETE));
        Assert.isTrue(getBaseMapper().update(null, new LambdaUpdateWrapper<JournalRenewal>().eq(JournalRenewal::getId, id).set(JournalRenewal::getDeletedFlag, true)) > 0,
                BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(String formID) {
        ApprovalForm approvalForm = approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单ID：" + formID));
        Assert.isTrue(approvalForm.getCreatedBy().equalsIgnoreCase(AccountHolder.getRequiredAccount()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_CREATED_BY));

        JournalRenewal journalRenewal = getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + approvalForm.getJournalId()));
        Assert.isTrue(
                ApprovalFormStateEnum.PENDING.name().equalsIgnoreCase(journalRenewal.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_PENDING)
        );
        getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRenewal>().eq(JournalRenewal::getId, approvalForm.getJournalId())
                        .set(JournalRenewal::getState, JournalStateEnum.DRAFT.name()));

        // 解锁学籍
        List<Object> recordIds = journalRenewalLineMapper.selectObjs(
                new LambdaQueryWrapper<JournalRenewalLine>().select(JournalRenewalLine::getOriginStudentRecordId)
                        .eq(JournalRenewalLine::getDeletedFlag, false)
                        .eq(JournalRenewalLine::getJournalRenewalId, approvalForm.getJournalId())
        );
        log.info("【撤销流程】查询基础班学籍ID列表：{}", JSON.toJSONString(recordIds));
        if (CollectionUtils.isNotEmpty(recordIds)) {
            studentRecordService.unlocks(
                    recordIds.stream().map(Object::toString).collect(Collectors.toSet()),
                    ReferenceTypeEnum.RENEWAL ,
                    approvalForm.getJournalId()
            );
            log.info("【撤销流程】解锁学籍ID列表：{}", JSON.toJSONString(recordIds));
        }
        approvalFormService.cancel(formID);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackRequester(String formID) {
        ApprovalForm approvalForm = approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单ID：" + formID));

        JournalRenewal journalRenewal = getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + approvalForm.getJournalId()));
        if (journalRenewal.getState().equals(JournalStateEnum.DRAFT.name())) {
            return;
        }
        getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRenewal>().eq(JournalRenewal::getId, approvalForm.getJournalId())
                        .set(JournalRenewal::getState, JournalStateEnum.DRAFT.name()));

        // 解锁学籍
        List<Object> recordIds = journalRenewalLineMapper.selectObjs(
                new LambdaQueryWrapper<JournalRenewalLine>().select(JournalRenewalLine::getOriginStudentRecordId)
                        .eq(JournalRenewalLine::getDeletedFlag, false)
                        .eq(JournalRenewalLine::getJournalRenewalId, approvalForm.getJournalId())
        );
        log.info("【撤销流程】查询基础班学籍ID列表：{}", JSON.toJSONString(recordIds));
        if (CollectionUtils.isNotEmpty(recordIds)) {
            studentRecordService.unlocks(
                    recordIds.stream().map(Object::toString).collect(Collectors.toSet()),
                    ReferenceTypeEnum.RENEWAL ,
                    approvalForm.getJournalId()
            );
            log.info("【撤销流程】解锁学籍ID列表：{}", JSON.toJSONString(recordIds));
        }
//        approvalFormService.cancel(formID);
    }


    private void addJournalRenewalOrderRelationship(StudentRecordChangeResultDTO renewal, JournalRenewalLineDTO line) {
        final String orderId = renewal.getOrderId();
        if (StringUtils.hasText(orderId)) {
            JournalRenewalOrderRelationship e = new JournalRenewalOrderRelationship();
            e.setOrderId(orderId);
            e.setJournalRenewalId(line.getJournalRenewalId());
            e.setJournalRenewalLineId(line.getId());
            e.setStudentRecordId(renewal.getTargetStudentRecordId());
            this.journalRenewalOrderRelationshipMapper.insert(e);
            log.info("【审批完成】增加升级凭单订单关系表：{}", JSON.toJSONString(e));
        }
    }

    @Override
    public List<JournalRenewalLineDiscountDO> findDiscountsByRenewalIdAndRecordId(String renewalId, String recordId) {
        return journalRenewalLineDiscountMapper.findDiscountsByRenewalIdAndRecordId(renewalId, recordId);
    }


    private List<JournalRenewalDiscount> wrapJournalRenewalDiscount(JournalRenewalDTO renewalDTO, Integer price) {
        if (Objects.isNull(renewalDTO) || CollectionUtil.isEmpty(renewalDTO.getDiscountList())) {
            return null;
        }
        int discountAmount = 0;
        List<JournalRenewalDiscount> list = new ArrayList<>();
        for (int i = 0; i < renewalDTO.getDiscountList().size(); i++) {
            JournalRenewalDiscountDTO discountDTO = renewalDTO.getDiscountList().get(i);
            EmsDiscounts discounts = emsDiscountsMapper.selectById(Integer.valueOf(discountDTO.getDiscountId()));
            JournalRenewalDiscount discount = new JournalRenewalDiscount();
            Assert.notNull(discounts, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠ID：" + discountDTO.getDiscountId()));
            int discountMoney = Objects.nonNull(discounts.getDiscountsMoney()) ? discounts.getDiscountsMoney().multiply(BigDecimal.valueOf(100)).intValue() : 0;
            discount.setDiscountType(DiscountTypeEnum.CASH.name());
            if (Objects.nonNull(discounts.getDiscountsRate()) && discounts.getDiscountsRate() > 0) {
                discountMoney = renewalDTO.getTargetClazzPrice().multiply(BigDecimal.valueOf(discounts.getDiscountsRate())).multiply(BigDecimal.valueOf(100)).intValue();
                discount.setDiscountType(DiscountTypeEnum.DISCOUNT.name());
            }
            discount.setDiscountId(discountDTO.getDiscountId());
            discount.setDiscountAmount(Objects.nonNull(price) ? Math.min(discountMoney, price - discountAmount) : discountMoney);
            discount.setDiscountName(discounts.getDiscountsName());
            discount.setJournalRenewalId(renewalDTO.getId());
            discount.setSort(i + 1);
            list.add(discount);
            discountAmount += discount.getDiscountAmount();
        }
        return list;
    }

    /**
     * <p>
     * 持久化 行数据 （凭单行、行优惠）
     * </p>
     *
     * @param renewalDTO     升级凭单DTO
     * @param discountMoney  凭单头优惠合计
     * @param isValidateTime 是否校验出、入班时间
     * @author zhouwei
     * @since 2023/12/12 7:05 PM
     **/
    public JournalRenewalProcessResultDTO wrapJournalRenewalLine(JournalRenewalDTO renewalDTO, int discountMoney,
                                                              Boolean isValidateTime, Boolean isValidatePersonalDiscount) {
        JournalRenewalProcessResultDTO resultDTO = new JournalRenewalProcessResultDTO()  ;
        if (Objects.isNull(renewalDTO) || CollectionUtil.isEmpty(renewalDTO.getLineList())) {
            return resultDTO;
        }
        boolean flag = true ;
        Integer price = renewalDTO.getTargetClazzPrice().multiply(BigDecimal.valueOf(100)).intValue();
        List<JournalRenewalLine> list = new ArrayList<>();
        List<JournalRenewalLineDiscount> lineDiscounts = new ArrayList<>() ;
        List<JournalLineErrorDTO> errorList =new ArrayList<>() ;
        for (int i = 0; i < renewalDTO.getLineList().size(); i++) {
            JournalRenewalLineDTO lineDTO = renewalDTO.getLineList().get(i);
            // 校验学籍ID是否为空、数据库存在
            JournalLineErrorDTO lineErrorDTO  = this.validateStudentRecord(lineDTO, lineDTO.getOriginStudentRecordId(),null ,renewalDTO.getTargetClazzId(),false);
            flag  =  flag  &&  Objects.isNull(lineErrorDTO)  ;
            if(Objects.nonNull(lineErrorDTO)) {
                errorList.add(lineErrorDTO) ;
            }
            Assert.notNull(lineDTO.getOriginClazzId(), BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "原班级ID"));
            JournalRenewalLine renewalLine = CustomBeanUtil.copyProperties(lineDTO, new JournalRenewalLine());
            log.info("【需要持久化的升级凭单行数据】详情：{}", JSON.toJSONString(renewalLine));
            renewalLine.setId(UUID.randomUUID().toString().replace("-", ""));
            // 出班、入班时间
            if (isValidateTime) {
                Assert.notNull(lineDTO.getOriginClazzOutDate(), BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "需要升班的基础班出班日期"));
                Assert.notNull(lineDTO.getTargetClazzInDate(), BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "升班目标班级预计入班日期"));
                Assert.isTrue(
                        lineDTO.getTargetClazzInDate().isAfter(lineDTO.getOriginClazzOutDate()) || lineDTO.getTargetClazzInDate().isEqual(lineDTO.getOriginClazzOutDate()) ,
                        BizExceptionProducer.produce(
                                StudentRecordErrorBodyEnum.IN_AFTER_OUT_DATE_ERROR, LocalDateTimeUtil.formatNormal(lineDTO.getTargetClazzInDate()),
                                LocalDateTimeUtil.formatNormal(lineDTO.getOriginClazzOutDate())
                        )
                );
            }
            // 优惠合计
            int lineDiscountAmount = discountMoney;
            // 优惠列表（普通优惠+ 个人优惠）
            List<JournalRenewalLineDiscountDTO> discountList = JournalRenewalUtil.getLineDiscountList(lineDTO);
            // 订单行优惠列表
            for (int k = 0; k < discountList.size(); k++) {
                // TO 如果 discount_scope_type 为 "PERSONAL"， 需要查询优惠凭单头中的优惠属性
                JournalRenewalLineDiscountDTO lineDiscountDTO = discountList.get(k);
                if (DiscountScopeTypeEnum.PERSONAL.name().equalsIgnoreCase(lineDiscountDTO.getDiscountScopeType())) {
                    JournalDiscountApiResDTO apiResDTO = this.getPersonDiscount(
                            renewalDTO.getTargetClazzId(),
                            lineDTO.getOriginStudentRecordId(),
                            lineDiscountDTO.getJournalDiscountLineId(),
                            isValidatePersonalDiscount
                    );
                    JournalRenewalUtil.transferJournalRenewalLineDiscountDTO(lineDiscountDTO, apiResDTO, null);
                } else {
                    EmsDiscounts discounts = emsDiscountsMapper.selectById(Integer.valueOf(lineDiscountDTO.getDiscountId()));
                    Assert.notNull(discounts, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠ID：" + lineDiscountDTO.getDiscountId()));
                    JournalRenewalUtil.transferJournalRenewalLineDiscountDTO(lineDiscountDTO, null, discounts);
                }
                JournalRenewalLineDiscount lineDiscount = JournalRenewalUtil.wrapJournalRenewalLineDiscount(
                    renewalDTO.getId(),
                    renewalLine.getId(),
                    lineDiscountDTO,
                    renewalDTO.getTargetClazzPrice(),
                    lineDiscountAmount,
                    k
                );
                // journalRenewalLineDiscountMapper.insert(lineDiscount);
                lineDiscounts.add(lineDiscount) ;
                lineDiscountAmount += lineDiscount.getDiscountAmount();
            }
            renewalLine.setTargetClazzOriginalAmount(price);
            renewalLine.setTargetClazzDiscountAmount(lineDiscountAmount);
            renewalLine.setTargetClazzAmountDue(price - renewalLine.getTargetClazzDiscountAmount());
            renewalLine.setJournalRenewalId(renewalDTO.getId());
            renewalLine.setState(renewalDTO.getState());
            // journalRenewalLineMapper.insert(renewalLine);
            list.add(renewalLine);
        }
        resultDTO.setLines(list);
        resultDTO.setLineDiscounts(lineDiscounts);
        resultDTO.setInsertFlag(flag);
        resultDTO.setLineErrorList(errorList);
        return resultDTO;
    }




    private JournalDiscountApiResDTO getPersonDiscount(Integer targetClazzId, String originStudentRecordId, String journalDiscountLineId, Boolean validate) {
        JournalDiscountApiResDTO apiResDTO = journalDiscountApi.queryByLineId(JournalDiscountApiReqDTO.buildForQueryByLineId(journalDiscountLineId));
        Assert.notNull(apiResDTO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "个人优惠行ID：" + journalDiscountLineId));
        if (validate) {
            Student student = studentMapper.findByRecordId(originStudentRecordId);
            Assert.notNull(student, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "学籍ID：" + originStudentRecordId + " 对应学生信息"));
            JournalDiscountApiReqDTO reqDTO = JournalDiscountApiReqDTO.buildForWriteOff(journalDiscountLineId, student.getId(), null, targetClazzId, LocalDateTime.now(), JournalDiscountApiReqDTO.Scenario.RENEWAL);
            Assert.isTrue(journalDiscountApi.validityVerify(reqDTO), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.PERSONAL_DISCOUNT_USED, journalDiscountLineId));
        }
        return apiResDTO;
    }

    /**
     * 校验学籍ID是否为空、切数据库存在
     *
     * @param renewalLineDTO 升级凭单行页面参数
     * @param originRecordId 基础班学籍ID
     * @param line  数据库凭单行信息
     * @param isCommit  是否提交操作
     */
    private JournalLineErrorDTO validateStudentRecord(JournalRenewalLineDTO renewalLineDTO, String originRecordId ,
                                       JournalRenewalLine line ,Integer targetClazzId , Boolean isCommit ) {
        JournalLineErrorDTO journalLineErrorDTO = null ;
        Assert.notNull(originRecordId, BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "需要升班的基础班学籍ID"));
        StudentRecord studentRecord = studentRecordMapper.selectById(originRecordId);
        Assert.notNull(studentRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "学籍ID：" + originRecordId));
        ClazzDTO clazzDO = clazzApi.queryByClassId(studentRecord.getClazzId());
        Assert.notNull(clazzDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "班级ID：" + studentRecord.getClazzId()));
        renewalLineDTO.setOriginClazzName(clazzDO.getClassName());
        Student student = studentMapper.findByRecordId(originRecordId);
        Assert.notNull(student, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "学籍ID：" + originRecordId + " 对应学生信息"));
        // 检查学籍”学籍操作锁“是否 为true ，如果是提示异常文案
        Assert.isTrue(!studentRecord.getLockFlag(),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.STUDENT_RECORD_LOCKED, "学籍ID：" + originRecordId + ",学号：" + student.getNumber()));
        Assert.isTrue(StudentRecordStateEnum.VALID.name().equalsIgnoreCase(studentRecord.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.STUDENT_RECORD_STATE_NO_VALID, studentRecord.getId()));
        // 如果是提交审批操作，检查当前升级目标班级 进班、出班时间 是否和之前学籍对应的 进班、出班时间出现交叉
        if (isCommit) {
            JournalInOutClazzDTO  inOutClazzDTO  = new JournalInOutClazzDTO(
                                                    targetClazzId ,
                                                    line.getOriginStudentRecordId() ,
                                                    LocalDateTime.of(line.getOriginClazzOutDate() ,LocalTime.MIN) ,
                                                    LocalDateTime.of(line.getTargetClazzInDate() ,LocalTime.MIN),
                                                    ReferenceTypeEnum.RENEWAL
            );
            journalLineErrorDTO = this.validateInOutDatetime(inOutClazzDTO );

        }
        return  journalLineErrorDTO ;
    }

    @Override
    public JournalLineErrorDTO validateInOutDatetime(@NonNull JournalInOutClazzDTO  inOutClazzDTO) {
        Assert.notNull(inOutClazzDTO.getReferenceTypeEnum() ) ;
        Assert.notNull(inOutClazzDTO.getTargetClazzInDate()) ;
        log.info("【检验进班、出班时间】 凭单行数据：{}" , JSON.toJSONString(inOutClazzDTO , SerializerFeature.DisableCircularReferenceDetect));
        List<StudentRecord> recordList = new ArrayList<>();
        StudentRecord studentRecord =  studentRecordMapper.selectById(inOutClazzDTO.getOriginStudentRecordId())  ;
        Assert.notNull(studentRecord , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"学籍ID：" + inOutClazzDTO.getOriginStudentRecordId() )) ;
        Student student = studentMapper.findByRecordId(inOutClazzDTO.getOriginStudentRecordId());
        Assert.notNull(student, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "学生ID：" + inOutClazzDTO.getOriginStudentRecordId()));

        final LocalDateTime originOutDateTime    =  inOutClazzDTO.getOriginClazzOutDate()  ;
        final LocalDateTime targetInDateTime = inOutClazzDTO.getTargetClazzInDate() ;
        ClazzDTO clazzDTO =  clazzApi.queryByClassId(inOutClazzDTO.getTargetClazzId()) ;
        Assert.notNull(clazzDTO ,  BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "班级ID："+  inOutClazzDTO.getTargetClazzId())) ;
        // 进班时间不能晚于预计结课时间
        Assert.isTrue(Objects.isNull(clazzDTO.getPredictionGraduationDate())
                || ! targetInDateTime.isAfter(LocalDateTime.of(clazzDTO.getPredictionGraduationDate() ,
                LocalTime.MIN)),BizExceptionProducer.produce(StudentRecordErrorBodyEnum.JOURNAL_IN_DATE_AFTER_PREDICTION_GRADUATION_DATE)
        );
        // 优先取页面输入的 【出班时间】 ，如果为空取 学籍【结课时间】
        studentRecord.setSettlementDate(
                Objects.nonNull(originOutDateTime) ?
                        originOutDateTime :
                        studentRecord.getSettlementDate()

        );
        recordList.add(studentRecord);
        // 如果是升级凭单 ，  需要校验历史学籍【进班时间】、【结课时间】
        // 排除开班时间 >= 3000-01-01 的学籍
        if (ReferenceTypeEnum.RENEWAL.equals(inOutClazzDTO.getReferenceTypeEnum())) {
            List<StudentRecord> studentRecords =  Objects.requireNonNullElse(
                    studentRecordService.findStudentRecordListByStudentId(student.getId(), Set.of(inOutClazzDTO.getOriginStudentRecordId())),
                    new ArrayList<>()
            ) ;
            // 过滤【进班时间】和【出班时间】 相同的历史学籍
            recordList.addAll(
                    studentRecords.stream()
                            .filter(item ->  StudentRecordStateEnum.VALID.name().equalsIgnoreCase(item.getState()))
                            .filter(item ->
                                    Objects.isNull(item.getInDate()) ||
                                    Objects.isNull(item.getSettlementDate()) ||
                                    !item.getInDate().isEqual(item.getSettlementDate())
                            )
                            .toList()
            );
        }
        log.info(
                "【检验进班、出班时间】 学生ID关联的学籍列表：{}" ,
                JSON.toJSONString(recordList ,SerializerFeature.DisableCircularReferenceDetect)
        );
        for (StudentRecord record : recordList) {
            ClazzDTO cl = clazzApi.queryByClassId(record.getClazzId());
            Assert.notNull(cl, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "班级ID：" + record.getClazzId()));
            //            Assert.isFalse(Objects.isNull(record.getSettlementDate()) && Objects.isNull(cl.getPredictionGraduationDate()),
            //                    BizExceptionProducer.produce(StudentRecordErrorBodyEnum.JOURNAL_NOT_ALLOW_COMMIT));
            if(Objects.isNull(record.getSettlementDate()) && Objects.isNull(cl.getPredictionGraduationDate())) {
                return new JournalLineErrorDTO(student.getId() ,student.getNumber() , StudentRecordErrorBodyEnum.JOURNAL_NOT_ALLOW_COMMIT.getMessage())   ;
            }
            LocalDateTime inTime  =  record.getInDate() ;
            LocalDateTime outTime = Objects.nonNull(record.getSettlementDate()) ?
                    record.getSettlementDate() :
                    LocalDateTime.of(cl.getPredictionGraduationDate(), LocalTime.MIN);
            Assert.isTrue(Objects.nonNull(inTime));
            log.info(
                    "【校验原班级进班时间、目标班级出班时间】 targetInDateTime：{},intTime：{},outTime：{} " ,
                    LocalDateTimeUtil.formatNormal(targetInDateTime),
                    LocalDateTimeUtil.formatNormal(inTime),
                    LocalDateTimeUtil.formatNormal(outTime)
            );
            // 过滤 目标班级【进班时间】和基础班【出班时间】相同的情况
            //            Assert.isTrue( (targetInDateTime.isAfter(outTime) || targetInDateTime.isEqual(outTime) )
            //                            && ( targetInDateTime.isAfter(inTime) ||  targetInDateTime.isEqual(inTime)) ,
            //                    BizExceptionProducer.produce(StudentRecordErrorBodyEnum.JOURNAL_NOT_ALLOW_COMMIT) );
            if(!(targetInDateTime.isAfter(outTime) || targetInDateTime.isEqual(outTime) )
                                        && ( targetInDateTime.isAfter(inTime) ||  targetInDateTime.isEqual(inTime)) ) {
                return new JournalLineErrorDTO(student.getId() ,student.getNumber() , StudentRecordErrorBodyEnum.JOURNAL_NOT_ALLOW_COMMIT.getMessage())   ;
            }
        }
        return  null ;
    }

    private void validateCreateParam(JournalRenewalDTO renewalDTO) {
        // 如果 升级凭单头ID不为空 ， 检查 状态是否为  ”草稿“、”审批未通过且流程结束“
        if (Objects.nonNull(renewalDTO.getId())) {
            JournalRenewal journalRenewal = getBaseMapper().selectById(renewalDTO.getId());
            log.info("【保存草稿】根据ID查询升级凭单头 ，ID：{}，查询结果：{}", renewalDTO.getId(), JSON.toJSONString(journalRenewal));
            Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单头ID：" + renewalDTO.getId()));
            // 只有草稿运行”保存草稿“ 和 ”提交“
            Assert.isTrue(JournalStateEnum.DRAFT.name().equalsIgnoreCase(journalRenewal.getState()),
                    BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_JOURNAL_RENEWAL_STATE_ERROR));
        }
        Itcastschool itcastschool = itcastschoolMapper.selectOne(
                new LambdaQueryWrapper<Itcastschool>().eq(Itcastschool::getRunstate, true)
                        .eq(Itcastschool::getCode, renewalDTO.getTargetClazzCampusId()));
        Assert.notNull(itcastschool, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_PARAM_SCHOOL_NO_EXIST, renewalDTO.getTargetClazzCampusId()));
        renewalDTO.setTargetClazzCampusName(itcastschool.getName());
        Subject subject = subjectMapper.selectOne(
                new LambdaQueryWrapper<Subject>().eq(Subject::getStatus, 1).eq(Subject::getId, renewalDTO.getTargetClazzSubjectId()));
        Assert.notNull(subject, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_PARAM_SUBJECT_NO_EXIST, renewalDTO.getTargetClazzSubjectId()));
        renewalDTO.setTargetClazzSubjectName(subject.getName());
        TeachingModelEnum teachingModelEnum = TeachingModelEnum.valueOf(renewalDTO.getTargetClazzModelId());
        renewalDTO.setTargetClazzModelName(teachingModelEnum.getName());

        ClazzDTO clazzDO = clazzApi.queryByClassId(renewalDTO.getTargetClazzId());
        Assert.notNull(clazzDO, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_PARAM_CLAZZ_NO_EXIST, renewalDTO.getTargetClazzId()));
        renewalDTO.setTargetClazzName(clazzDO.getClassName());

        BigDecimal tuitionFee = tuitionFeeService.findTuition(renewalDTO.getTargetClazzId(), null);
        Assert.notNull(tuitionFee, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_PARAM_TUITION_NO_EXIST, renewalDTO.getTargetClazzId()));
        renewalDTO.setTargetClazzPrice(tuitionFee);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String getStrategyName() {
        return JournalType.RENEWAL.name();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inform(String formID, String targetAssignee) {
        approvalFormFlowService.inform(formID, targetAssignee);
    }

    //    @Override
    //    public  void testFinish(String formId) {
    //        this.finish(formId);
    //    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Runnable finish(String formID) {
        ApprovalForm approvalForm = approvalFormService.getByBusinessKey(formID);
        String journalId = approvalForm.getJournalId();
        log.info("【审批完成】查询审批单详情：{}", JSON.toJSONString(approvalForm));
        // 处理凭单自己的业务逻辑
        final JournalRenewalDTO journalRenewalDTO = this.findById(journalId);
        log.info("【审批完成】查询升级凭单详情：{}", JSON.toJSONString(journalRenewalDTO));
        final List<JournalRenewalLineDTO> lines = this.journalRenewalLineService.findListByRenewalId(journalId);
        log.info("【审批完成】查询升级凭单行列表：{}", JSON.toJSONString(lines));
        journalRenewalDTO.setLineList(lines);
        final Set<String> srIds = lines.stream().map(JournalRenewalLineDTO::getOriginStudentRecordId).collect(Collectors.toSet());
        final ClazzDTO clazzDTO  = clazzApi.queryByClassId(journalRenewalDTO.getTargetClazzId())  ;
        Assert.notNull(clazzDTO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "班级ID：" +  journalRenewalDTO.getTargetClazzId())) ;
        Set<String> studentRecordIds = lines.stream().map(JournalRenewalLineDTO::getOriginStudentRecordId).collect(Collectors.toSet());
        final Map<String, StudentRecord> studentRecordMap = Objects.requireNonNullElse(studentRecordService.getByIds(studentRecordIds), new ArrayList<StudentRecord>())
                .stream()
                .collect(Collectors.toMap(StudentRecord::getId, Function.identity()));
        // 凭单头优惠列表
        List<Integer> discountIdsList = Objects.requireNonNullElse(journalRenewalDTO.getDiscountList(), new ArrayList<JournalRenewalDiscountDTO>())
                .stream()
                .map(item -> Integer.valueOf(item.getDiscountId()))
                .toList();
        // 行优惠列表
        List<JournalRenewalLineDiscountDO> lineDiscountList = journalRenewalLineDiscountMapper.findDiscountsByRenewalId(journalRenewalDTO.getId(), null);
        final Map<String, List<JournalRenewalLineDiscountDO>> lineDiscountMap = lineDiscountList
                .stream()
                .collect(Collectors.groupingBy(JournalRenewalLineDiscountDO::getJournalRenewalLineId, Collectors.toList()));
        // 将升级凭单学籍信息同步至老EMS
        List<StudentRecordCirculation> circulations = JournalRenewalUtil.wrapCirculations(journalRenewalDTO, discountIdsList, studentRecordMap, lineDiscountMap, clazzDTO);
        log.info("【审批完成】升级凭单同步至老EMS参数：{}" ,JSON.toJSONString(circulations ,SerializerFeature.DisableCircularReferenceDetect));
        final EmsApiResult apiResult = oldEmsSyncService.circulations(circulations);
        log.info("【审批完成】升级凭单同步至老EMS响应正文：{}", apiResult);
        final Map<String, Integer> srStudentIds = this.studentRecordService.findStudentIds(srIds);
        lines.forEach(line -> {
            line.setStudentId(srStudentIds.get(line.getOriginStudentRecordId()));
            final StudentRecordChangeDTO renewalDTO = StudentRecordConverter.renewalDTO(journalRenewalDTO, line);
            log.info("【审批完成】查询升级凭单行升级学籍：{}", JSON.toJSONString(renewalDTO));
            final StudentRecordChangeResultDTO renewal = studentRecordRenewalService.renewal(renewalDTO);
            log.info("【审批完成】处理升级后学籍和订单结果：{}", JSON.toJSONString(renewal));
            if (Objects.nonNull(renewal)) {
                this.addJournalRenewalOrderRelationship(renewal, line);
            }
        });
        getBaseMapper().update(null,
                new LambdaUpdateWrapper<JournalRenewal>()
                        .eq(JournalRenewal::getId, journalId)
                        .set(JournalRenewal::getState, ApprovalFormStateEnum.FINISHED.name()));
        log.info("【审批完成】修改升级凭单状态 ID：{}， 修改状态：{}", journalId, ApprovalFormStateEnum.FINISHED.name());
        // 处理本地的审批流程单
        approvalFormService.finish(formID);
        log.info("【重读凭单审批】审批完成，处理结束 formID：{} , journalId：{}", formID, journalId);
        return ()->  updateStudentRecordOldRecordId(journalId);
    }


    private void updateStudentRecordOldRecordId(String journalId) {
        Set<String> studentRecordIds = Objects.requireNonNullElse(journalRenewalLineService.findListByRenewalId(journalId), new ArrayList<JournalRenewalLineDTO>())
                .stream().map(JournalRenewalLineDTO::getTargetStudentRecordId)
                .collect(Collectors.toSet());
        log.info("【升级凭单-审批完成】维护老EMS学籍：{}" ,JSON.toJSONString(studentRecordIds));
        studentRecordService.syncSjc(studentRecordIds);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(String formID, ApproveResult result, String comment, final String form, Integer applyType) {
        ApprovalForm approvalForm = approvalFormService.getById(formID);
        JournalRenewal journalRenewal = getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + approvalForm.getJournalId()));
        Assert.isTrue(ApprovalFormStateEnum.PENDING.name().equalsIgnoreCase(journalRenewal.getState())
                        || ApprovalFormStateEnum.APPROVING.name().equalsIgnoreCase(journalRenewal.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_PENDING_APPROVING));
        String journalId = approvalForm.getJournalId();
        final List<JournalRenewalLineDTO> lines = this.journalRenewalLineService.findListByRenewalId(journalId);
        final Set<String> srIds = lines
                    .stream()
                    .map(JournalRenewalLineDTO::getOriginStudentRecordId)
                    .collect(Collectors.toSet());
        if (Objects.requireNonNull(result) == ApproveResult.REJECT) {
            getBaseMapper().update(
                    null,
                    new LambdaUpdateWrapper<JournalRenewal>()
                            .eq(JournalRenewal::getId, approvalForm.getJournalId())
                            .set(JournalRenewal::getState, ApprovalFormStateEnum.REJECTED)
            );

            this.studentRecordService.unlocks(srIds, ReferenceTypeEnum.RENEWAL, approvalForm.getJournalId());
            JournalRenewal updateEntity = new JournalRenewal();
            updateEntity.setId(formID);
            updateEntity.setState(ApprovalFormStateEnum.REJECTED.name());
            super.updateById(updateEntity);
        } else {
            JournalStateEnum journalStateEnum = JournalStateEnum.APPROVING;
            //  1：审批（默认））2：驳回到发起人）3：驳回上一级）
            if (applyType != null && applyType == 2) {
                journalStateEnum = JournalStateEnum.DRAFT;
                this.studentRecordService.unlocks(srIds, ReferenceTypeEnum.RENEWAL, approvalForm.getJournalId());
            } else if (applyType != null && applyType == 3) {
                journalStateEnum = JournalStateEnum.PENDING;
            }
            getBaseMapper().update(null,
                    new LambdaUpdateWrapper<JournalRenewal>().eq(JournalRenewal::getId, journalRenewal.getId())
                            .set(JournalRenewal::getState, journalStateEnum.name()));
        }
        // 处理审批单的业务逻辑
        approvalFormService.approve(formID, result, comment, form, applyType);
    }

    @Override
    public void remind(String formID, String targetAssignee) {
        String result = approvalFormService.remind(formID, targetAssignee);
        log.info("【调用工作流催办接口】响应正文：" + result);
    }

    @Override
    public JournalRenewalDTO getById(@NonNull String id) {
        JournalRenewal journalRenewal = getBaseMapper().selectById(id);
        Assert.notNull(journalRenewal, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "升级凭单ID：" + id));
        return CustomBeanUtil.copyProperties(journalRenewal, new JournalRenewalDTO());
    }
}
